package ipfs

import (
	"context"
	"fmt"
	"github.com/cihub/seelog"
	"github.com/ipfs/boxo/files"
	"github.com/ipfs/kubo/client/rpc"
	"github.com/ipfs/kubo/core/commands/cmdutils"
	ma "github.com/multiformats/go-multiaddr"
	"kyland.com/eagle/lib/config"
	"kyland.com/eagle/lib/exception"
	"os"
	"strconv"
)

const (
	DEFAULT_PORT = 5001
	URI          = "/api/v0/add"
)

type IpfsApi struct {
	Api *rpc.HttpApi
}

var (
	Client *IpfsApi
)

func Init() {
	Client = NewIpfsClient()
}

func NewIpfsClient() *IpfsApi {
	ip := os.Getenv("NODE_IP")
	ipfs, err := config.Config.Get("ipfs")
	if err != nil {
		errMsg := fmt.Sprintf("get ipfs config failed: %v", err)
		exception.Throw(errMsg)
	}

	port := ipfs.UString("port")
	dest := fmt.Sprintf("/ip4/%s/tcp/%s", ip, port)
	addr, err := ma.NewMultiaddr(dest)
	if err != nil {
		errMsg := fmt.Sprintf("create ipfs dest %s %v failed: %v", dest, addr, err)
		exception.Throw(errMsg)
	}
	api, err := rpc.NewApi(addr)
	if err != nil {
		errMsg := fmt.Sprintf("create ipfs %s failed: %v", dest, err)
		exception.Throw(errMsg)
	}

	ipfsApi := &IpfsApi{api}

	return ipfsApi
}

func port() string {
	ipfs, err := config.Config.Get("ipfs")
	if err != nil {

		return ""
	}
	port := ipfs.UString("port")
	return port
}

func getUnixfsNode(path string) (files.Node, error) {
	st, err := os.Stat(path)
	if err != nil {
		return nil, err
	}

	f, err := files.NewSerialFile(path, false, st)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func IpfsUri() string {
	ip := os.Getenv("NODE_IP")
	ipfs, err := config.Config.Get("ipfs")
	if err != nil {
		return strconv.Itoa(DEFAULT_PORT)
	}

	port := ipfs.UString("port")
	return fmt.Sprintf("http://%s:%d%s", ip, port, URI)
}

func (api *IpfsApi) Add(path string) (string, error) {
	someFile, err := getUnixfsNode(path)
	if err != nil {
		seelog.Errorf("could not get File %s: %s", path, err)
		return "", err
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	cidFile, err := api.Api.Unixfs().Add(ctx, someFile)
	if err != nil {
		seelog.Errorf("could not add File: %s", err.Error())
		return "", err
	}

	return cidFile.RootCid().String(), nil
}

func (api *IpfsApi) Get(cid, outputPathFile string) error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	p, err := cmdutils.PathOrCidPath(cid)
	if err != nil {
		seelog.Errorf("get file path from cid: %s failed\n", cid)
		return err
	}

	rootNodeFile, err := api.Api.Unixfs().Get(ctx, p)
	if err != nil {
		seelog.Errorf("could not get file with CID: %s", err)
		return err
	}

	if fileExists(outputPathFile) {
		err := os.Remove(outputPathFile)
		if err != nil {
			seelog.Errorf("Error deleting file:", err)
		}
	}

	err = files.WriteTo(rootNodeFile, outputPathFile)
	if err != nil {
		seelog.Errorf("could not write out the fetched CID: %s", err)
		return err
	}

	return nil
}

func fileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}
